【マルチアカウント編】EC2 Image Builder で作られたイメージ(AMI)を自動的に SSMパラメータストアに格納する

【マルチアカウント編】EC2 Image Builder で作られたイメージ(AMI)を自動的に SSMパラメータストアに格納する

Clock Icon2021.03.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

EC2 Image Builder(以下 Image Builder)のパイプラインから EC2イメージ(AMI)を作成したときに、 そのAMI IDを Systems Manager(SSM)パラメータストアに自動的に格納するような構成を作っていきます。

前回は 1アカウント内で完結するケースを書きました。

今回は「マルチアカウント編」ということで、 イメージ配布(共有)先アカウントの SSMパラメータに格納する 構成を作ってみます。

図で表すと以下のとおりです。

img

  1. Image Builderパイプライン で AMI作成・配布時に SNSトピックを通知するように設定しておきます
  2. このSNSトピック通知をトリガーに Lambda関数を実行します
  3. このLambda関数で「各アカウントのSSMパラメータストア」に AMI IDの情報を登録( PutParameter )します

構成

大枠は 前回の1アカウント編のブログ と変わりません。 共通部分は簡単に説明します。

EC2 Image Builder

パイプラインの インフラストラクチャ設定 > SNS 部分を設定しておきます。

img

ディストリビューション設定 部分でイメージ共有の設定を行います。

img

このSNS設定/ディストリビューション設定により、AMI作成・配布が完了したときに、以下のようなメッセージを 通知することができます。

{
  "versionlessArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe",
  "semver": 1073741827,
  "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3",
  "name": "sample-recipe",
  (略)
  "distributionConfiguration": {
    "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-xxxx",
    "name": "sample-al2-ami-xxxx",
    "dateCreated": "Mar 5, 2021 1:14:50 AM",
    "dateUpdated": "Mar 9, 2021 2:56:29 AM",
    "distributions": [
      {
        "region": "ap-northeast-1",
        "amiDistributionConfiguration": {
          "launchPermission": {
            "userIds": [
              "111111111111",
              "222222222222"
            ]
          }
        }
      }
    ],
    "tags": {
      "internalId": "6520c90f-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-xxxx"
    },
    "accountId": "123456789012"
  },
  (略)
  "outputResources": {
    "amis": [
      {
        "region": "ap-northeast-1",
        "image": "ami-01xxxxxxxxxxxxxxx",
        "name": "sample-recipe 2021-03-05T01-16-05.489Z",
        "accountId": "123456789012"
      }
    ]
  },
  (略)
}

ハイライト部分 distributionConfiguration が配布設定です。後述の Lambda 関数で利用します。

IAMロール

img

上記のように IAMロールを 2種類 作成する必要があります。

  • ロールA
    • Lambdaの実行アカウントに作成する
    • AWSLambdaBasicExecutionRole (AWS管理ポリシー) を付与
    • ロールBに AssumeRole を行う権限を付与
  • ロールB
    • 配布先アカウントにそれぞれ作成する
    • ロールAが AssumeRole できるようする(信頼ポリシー)
    • SSMパラメータを作成/更新する権限を付与

今回のブログではロールBの名前を LambdaExecutionRoleForSSMParam としています。

Lambda 関数

ランタイム: Python3.7 で作成しています。

以下のようなコードを作成しました。

import json
import boto3
from boto3.session import Session
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

REGION = "ap-northeast-1"
ROLE_NAME = "LambdaExecutionRoleForSSMParam"

sts = boto3.client('sts')


def lambda_handler(event, context):
    message = json.loads(event["Records"][0]["Sns"]["Message"])
    process_sns_message(message)


def process_sns_message(message):
    logger.info("Printing message: {}".format(message))
    # state キーが無いとき、もしくは state.status が "AVAILABLE" でないときはパラメータストアに登録しない
    if message.get('state') == None or message['state'].get('status') != "AVAILABLE":
        return None

    # レシピ名, AMI ID, 配布情報を取得
    recipe_name = message['name']
    ami = message['outputResources']['amis'][0]
    dists = message['distributionConfiguration']['distributions']
    logger.info("recipe_name={}".format(recipe_name))
    logger.info("ami={}".format(ami))
    logger.info("dists={}".format(dists))

    # 配布先アカウントの SSMパラメータに登録
    for dist in filter(lambda d: d.get('region') == REGION, dists):
        for user_id in dist['amiDistributionConfiguration']['launchPermission']['userIds']:
            logger.info(
                "executing ssm:PutParameters to user:{}".format(user_id))
            put_param(recipe_name, ami, user_id)


def put_param(recipe_name, ami, user_id):
    # 一時情報の取得
    remote = sts.assume_role(
        RoleArn="arn:aws:iam::{}:role/{}".format(user_id, ROLE_NAME),
        RoleSessionName="imagebuilder_master"
    )
    ssm = boto3.client(
        'ssm',
        aws_access_key_id=remote['Credentials']['AccessKeyId'],
        aws_secret_access_key=remote['Credentials']['SecretAccessKey'],
        aws_session_token=remote['Credentials']['SessionToken']
    )

    # SSMパラメータストアに登録
    response = ssm.put_parameter(
        Name="/ec2-imagebuilder/latest/{}".format(recipe_name),
        Description="Latest AMI ID:{}".format(recipe_name),
        Value=ami['image'],
        Type='String',
        Overwrite=True,
        Tier='Standard'
    )
    logger.info(
        "[put_param] ssm.put_parameter response: {}".format(response))

受け取った SNSメッセージをのステータス、AMI ID、レシピ名、配布情報を取得して、 配布先アカウントそれぞれに SSMパラメータ ( "/ec2-imagebuilder/latest/{レシピ名}" ) に登録する処理を行います。

▼ ほか設定: SNS

Image Builder のインフラストラクチャ設定で指定したSNSトピックをトリガーとします。

▼ ほか設定: IAMロール

前述の ロールA を付与します。 AssumeRole 部分のポリシーは以下のようになります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Resource": "arn:aws:iam::*:role/LambdaExecutionRoleForSSMParam",
            "Action": "sts:AssumeRole",
        }
    ]
}

構築

(事前準備) 配布先アカウントのIAMロール

前述の ロールB (LambdaExecutionRoleForSSMParam) を配布先アカウントそれぞれに作成します。 以下 CloudFormation(CFn)テンプレートを作成しました。

    
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  MainAccountId:
    Description: "Main AWS account ID to run Lambda function"
    Type: String
Resources:
  Role:
    Type: AWS::IAM::Role
    Properties: 
      RoleName: LambdaExecutionRoleForSSMParam
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              "AWS": !Sub "arn:aws:iam::${MainAccountId}:root"
      Policies: 
        - PolicyName: policy-for-ssm-action
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: SSMPutParameter
                Effect: "Allow"
                Action: "ssm:PutParameter"
                Resource: "*"

個別に展開、もしくは CFn StackSets 展開しておきます。

SAMで構築

SNSトピックとLambda関数(+ロールA)の部分AWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。

▼ プロジェクト

sam init で新規プロジェクトを作成します。

sam init --runtime python3.7 --name tracking-latest-images-in-imagebuilder-cross-account

▼ template.yaml

必要なリソースを記述した template.yaml は以下のとおり。

 
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "tracking the latest AMI ID in EC2 Image Builder pipeline"
Resources:
  # SNS topic
  SnsTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: topic-for-imagebuilder
  # Lambda function
  Function:
    Type: AWS::Serverless::Function
    Properties:
      Description: "Update SSM Parameter with the latest AMI ID"
      CodeUri: scripts/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        EventBridgeRule:
          Type: SNS
          Properties:
            Topic: !Ref SnsTopic
      Policies:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - Version: '2012-10-17'
          Statement:
            - Sid: STSAssumeRole
              Effect: "Allow"
              Action: "sts:AssumeRole"
              Resource: "arn:aws:iam::*:role/LambdaExecutionRoleForSSMParam"

▼ scripts/app.py

前述のLambda関数のコードを scripts/app.py に格納します。

▼ ビルド、デプロイ

sam build
sam deploy --guided

特にパラメータ指定していないので、ガイド通りに YES 選択でデプロイできます。

確認

事前に ImageBuilderのパイプラインの インフラストラクチャ設定 > SNS 部分 に、 作成した SNSトピックを指定しておきます。

パイプライン実行

Image Builder のパイプラインを実行します。 [使用可能] になるまで待ちます。

img

配布先アカウントの確認

まず、EC2の AMI画面を見てみます。Image Builder からのAMI共有を確認できました。

img

次に配布先のアカウントの SSMパラメータを見てみます。

img

AMI ID が登録されていること確認できました。

おわりに

EC2 Image Builder で作った最新AMIを「配布先アカウントの SSMパラメータストア」に 自動登録する仕組みを作ってみました。

マルチアカウント環境では AMIを他アカウントに共有する機会が多いと思います。 配布されたAMI および作成されたSSMパラメータを使って、 EC2インスタンスを作成する際の AMI ID 参照などに活用できると思います。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.